/*
 
 RFA Manager, Refractor2 Archive editor.
 Copyright (C) 2008
 Torsten Kammer         torsten@ferroequinologist.de
 
 Based on:
 RAT dcom v0.5.0, Refractor2 Archive Tool decompressor
 
 Copyright (C) 2002
 Tonto Rostenfaunt		tonto@linuxmail.org
 Blackjap McGee			blackjap@hawaii.rr.com
 
 This program is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License
 as published by the Free Software Foundation; either version 2
 of the License, or (at your option) any later version.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 
 */

#import "RFADecompression.h"
#import "NSDataRFAAdditions.h"

// rfacpy, seems to copy in four byte amounts, doing some stuff with length.
void rfacpy(unsigned char *dest, unsigned char *source, unsigned int length)
{
	length+=3;
	length>>=2;
	for(;length!=0;length--,source+=4,dest+=4) {
		dest[0]=source[0]; dest[1]=source[1];
		dest[2]=source[2]; dest[3]=source[3];
	}
}

// The infamous _dcom method from the r2at project. I don't know what it does. If you have a better
// solution, one that works on PowerPC as well, preferrably, I'd be extremely happy to hear it.

// The Macros are defined to not expand, because I don't understand what they say anyway.
#define OOB
#define DBUG(id)

int _dcom(const unsigned char *sbuff, unsigned char *obuff, unsigned int sbsize, unsigned int obsize)
{
    unsigned int  cb1=0,cbc1=0,distance=0,length=0;
    const unsigned char *sb1;
    unsigned char *ob1,*ob2;
    char *cb1_8=(char*)&cb1,*cb1_16=(char*)&cb1;
    unsigned int sbend=sbsize-3;
    sb1=sbuff; ob1=obuff; ob2=obuff; cb1_16++;
    
    *cb1_8=*sb1++;
    
    if(cb1>17) {
        DBUG(1);
        cb1-=14;
        if(ob1+(cb1-3)>obuff+obsize) { OOB; return(-5); }
        if(sb1+(cb1-3)>sbuff+sbend) { OOB; return(-4); }
        cbc1=cb1>>2;
        cb1^=3;
        cb1&=3;
        DBUG(2);
        for(;cbc1!=0;cbc1--,sb1+=4,ob1+=4) {
            ob1[0]=sb1[0]; ob1[1]=sb1[1];
            ob1[2]=sb1[2]; ob1[3]=sb1[3];
        }
        sb1-=cb1;
        ob1-=cb1;
        *cb1_8=*sb1++;
    } else { goto firstcontrol; }
    
    
    while(1) {
    START:
        if(cb1>=0x40) {
            distance=((cb1>>2)&7)+(*sb1++)*8;
            length=(cb1>>5)+1;
            ob2=ob1-(distance+1);
            if(ob2<obuff) { OOB; return(-6); }
            if(distance<3) {
                if(ob1+length>obuff+obsize) { OOB; return(-5); }
                for(;length!=0;length--,ob1++,ob2++)
                    *ob1=*ob2;
            } else {
                rfacpy(ob1,ob2,length);
                ob1+=length;
            }
            length=distance=0;
        }
        else if(cb1>=0x20) {
            length=cb1&31;
            if(length==0) {
                while((*sb1++)==0) {
                    length+=255;
                    if(sb1+3>sbuff+sbend) { OOB; return(-4); }
                }
                length+=(*(sb1-1))+33;
            } else length+=2;
            distance=(*(unsigned short int *)&sb1[0])>>2;
            sb1+=2;
            ob2=ob1-(distance+1);
            if(ob2<obuff) { OOB; return(-6); }
            if(distance<3) {
                if(ob1+length>obuff+obsize) { OOB; return(-5); }
                for(;length!=0;length--,ob1++,ob2++)
                    *ob1=*ob2;
            } else {
                rfacpy(ob1,ob2,length);
                ob1+=length;
            }
            length=distance=0;
        } else if(cb1>=0x10) {
            length=cb1&7;
            if(length==0) {
                while((*sb1++)==0) {
                    length+=255;
                    if(sb1+3>sbuff+sbend) { OOB; return(-14); }
                }
                length+=(*(sb1-1))+9;
            }else length+=2;
            distance=(*(unsigned short int *)&sb1[0]);
            sb1+=2;
            ob2=ob1-16384;
            if((distance>>=2)==0) {
                if(sb1==(sbuff+sbend+3)) { return -12; }
                if(ob1==(obuff+obsize)) { return -12; }
                if(ob1>obuff+obsize) { OOB; return(-5); }
                return((sb1>sbuff+sbend?-4:-8));
            }
            ob2-=distance;
            rfacpy(ob1,ob2,length);
            ob1+=length;
            length=distance=0;
        } else if(cb1<0x10) {
            if(ob1>obuff+obsize) { OOB; return(-5); }
            distance=(cb1>>2)+(*sb1++)*4;
            ob2=ob1-(distance+1);
            if(ob2<obuff) { OOB; return(-6); }
            ob1[0]=ob2[0];
            ob1[1]=ob2[1];
            //		 memcpy(ob1,ob2,2);
            ob1+=2;
        }
        
        cb1=*(sb1-2)&3;
        
        while(cb1==0) {
            if(sb1>sbuff+sbend) { OOB; return(-4); }
            cb1=*sb1++;
        firstcontrol:
            if(cb1>=0x10) goto START;
            if(cb1==0) {
                while((*sb1++)==0) {
                    cb1+=255;
                    if(sb1+cb1+18>sbuff+sbend) { OOB; return(-4); }
                }
                cb1+=*(sb1-1)+21;
            } else cb1+=6;
            if(ob1+cb1-3>obuff+obsize) { OOB; return(-5); }
            if(sb1+cb1-3>sbuff+sbend) { OOB; return(-4); }
            length=(cb1>>2);
            cb1^=3;
            cb1&=3;
            length=(length*4)-cb1;
            memcpy(ob1,sb1,length);
            sb1+=length;
            ob1+=length;
            length=0;
            cb1=*sb1++;
            if(cb1>=0x10) goto START;
            if(ob1+3>obuff+obsize) { OOB; return(-5); }
            distance=(cb1>>2)+(*sb1++)*4;
            ob2=ob1-(distance+1)-2048;
            if(ob2<obuff) { OOB; return(-6); }
            memcpy(ob1,ob2,4);
            ob1+=3;
            cb1=*(sb1-2)&3;
        }
        if(ob1+cb1>obuff+obsize) { OOB; return(-5); }
        if(sb1+cb1>sbuff+sbend) { OOB; return(-4); }
        memcpy(ob1,sb1,4);
        sb1+=cb1;
        ob1+=cb1;
        cb1=*sb1++;
    }
    return 0;
}

@implementation RFAArchive (Decompression)

+ (NSData *)_decompressedDataFromData:(NSData *)compressedData decompressedLength:(NSUInteger)length;
{
    //TODO: Decompression is not supported on PowerPC.
    if (NSHostByteOrder() == NS_BigEndian) return nil;
	
	// Create out data
	NSMutableData *resultData = [NSMutableData data];
	// Go through the segments. A compressed file is seperated into multiple segments, so first of all one has to
	// read the segment header.
	unsigned numSegments = [compressedData littleIntAtPosition:0];
	NSUInteger offset = 4;
	unsigned i;
	for (i = 0; i < numSegments; i++)
	{
		unsigned compressedSize = [compressedData littleIntAtPosition:offset]; offset += 4;
		unsigned uncompressedSize = [compressedData littleIntAtPosition:offset]; offset += 4;
		unsigned dataOffset = [compressedData littleIntAtPosition:offset]; offset += 4;
		
		// The buffer has to be way too large as _dcom has a habit of writing stuff where it shouldn't. Gaah!
		unsigned char *segmentOutBuffer = malloc(length * 10);
		if (!segmentOutBuffer) return nil;
		
		NSData *segmentData = [compressedData subdataWithRange:NSMakeRange(numSegments * 12 + 4 + dataOffset, compressedSize)];
		int result = _dcom(segmentData.bytes, segmentOutBuffer, compressedSize, uncompressedSize);
		if (result != -12)
		{
			free(segmentOutBuffer);
			return nil;
		}
		
		[resultData appendBytes:segmentOutBuffer length:uncompressedSize];
		free(segmentOutBuffer);
	}
    
	return [resultData copy];
}

@end

